package com.example.mq.mqclient;

import com.example.mq.common.*;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class Connection {
    private Socket socket = null;

    // 管理这个连接中的 channel，key：channelId
    private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();

    private final InputStream inputStream;
    private final OutputStream outputStream;
    private final DataInputStream dataInputStream;
    private final DataOutputStream dataOutputStream;

    // 这个线程池用来执行回调函数
    private final ExecutorService callbackPool;

    // 建立连接
    public Connection(String host, int port) throws IOException {
        // 建立 TCP 连接
        socket = new Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);

        callbackPool = Executors.newCachedThreadPool();

        // 创建扫描线程，从 Socket 中读取响应，将响应交给对应的 Channel
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    // 读取响应
                    Response response = readResponse();
                    // 解析响应
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                log.info("连接正常断开！");
            } catch (IOException | ClassNotFoundException | MQException e) {
                log.info("连接异常断开！");
                e.printStackTrace();
            }
        });
        t.start();
    }

    // 关闭 Connection，释放资源
    public void close() {
        try {
            callbackPool.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析响应，有两种响应
     * 1. 调用服务器方法返回的响应
     * 2. 服务器推送的消息
     */
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MQException {
        if (response.getType() == 0xc) {
            // 1. 这个响应是服务器推送的消息
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            // 根据 channelId 找到对应的 channel 对象
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if (channel == null) {
                throw new MQException("[connection] 该消息对应的 Channel 在中不存在！channelId=" + subScribeReturns.getChannelId());
            }
            // 执行这个 channel 对象中的回调函数，处理消息
            callbackPool.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(),
                            subScribeReturns.getBasicProperties(), subScribeReturns.getBody());
                } catch (MQException | IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            // 2. 这个响应是调用方法的返回响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            // 根据 channelId 拿到对应的 channel 对象
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if (channel == null) {
                throw new MQException("[connection] 该响应对应 Channel 在中不存在！channelId=" + basicReturns.getChannelId());
            }
            // 将这个响应，放入这个 channel 对象的响应列表中
            channel.putReturns(basicReturns);
        }
    }

    // 发送请求
    public void writeRequest(Request request) throws IOException {
        synchronized (dataOutputStream) {
            dataOutputStream.writeInt(request.getType());
            dataOutputStream.writeInt(request.getLength());
            dataOutputStream.write(request.getPayload());
            dataOutputStream.flush();
            log.info("发送请求！type=" + request.getType() + ", length=" + request.getLength());
        }
    }

    // 读取响应
    public Response readResponse() throws IOException {
        Response response = new Response();
        byte[] payload;
        int n;
        synchronized (dataInputStream) {
            response.setType(dataInputStream.readInt());
            response.setLength(dataInputStream.readInt());
            payload = new byte[response.getLength()];
            n = dataInputStream.read(payload);
        }
        if (n != response.getLength()) {
            System.out.println("type=" + response.getType());
            System.out.println("n=" + n);
            System.out.println("length=" + response.getLength());
            throw new IOException("读取的响应格式出错！");
        }
        response.setPayload(payload);
        log.info("收到响应！type=" + response.getType() + ", length=" + response.getLength());
        return response;
    }

    // 在当前的 Connect 中，创建一个 Channel
    public Channel createChannel() throws IOException {
        // 1. 创建 Channel 对象
        String channelId = "C-" + UUID.randomUUID();
        Channel channel = new Channel(channelId, this);
        // 2. 存入当前 Connection 管理的 Channel 表中
        channelMap.put(channelId, channel);
        // 3 发送创建 channel 请求
        boolean ok = channel.createChannel();
        if (!ok) {
            // 创建失败
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }
}
